﻿//============================================================================================================
// Microsoft Updater Application Block for .NET
//  http://msdn.microsoft.com/library/en-us/dnbda/html/updater.asp
//	
// WaitForApplicationExitProcessor.cs
//
// This file contains the ExternalApplicationExit processor.
// 
// For more information see the Updater Application Block Implementation Overview. 
// 
//============================================================================================================
// Copyright © Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//============================================================================================================

using System;
using System.Configuration;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Xml;
using Microsoft.ApplicationBlocks.Mobile.Updater.Activator;
using Microsoft.ApplicationBlocks.Mobile.Updater.Configuration;
using Microsoft.ApplicationBlocks.Mobile.Updater.Xsd;
using OpenNETCF.Threading;

namespace Microsoft.ApplicationBlocks.Mobile.Updater.ActivationProcessors
{
	/// <summary>
	/// Launches an external process to complete the application updates. .
    /// Checks whether the application is already executing and postpones the activation until it is closed.
	/// </summary>
	/// <remarks>
	/// As the updating application is running, some of its files may be locked. 
	/// When this processor is called from the updating application, it starts a new process
	/// that waits for the application to be closed to resume the activation process.
	/// 
	/// These attributes are defined to configure this processor. They are mandatory except where noted.
	/// <list type="table">
	/// <listheader><term>Attribute</term><description>Description</description></listheader>
	/// <item><term>type</term><description>The type name for this processor</description></item>
	/// <item><term>processName</term><description>Optional. The name of the process to wait for. When used, the processor will wait for the termination of the specified process instead of the updating application</description></item>
	/// </list>	
	/// </remarks>
	/// <example>
	/// The following XML excerpt demonstrates how to configure this processor in the manifest file.
	/// <code>
	/// &lt;manifest manifestId="{311085F7-9320-4318-9A67-9BE32F04E933}" mandatory="False" xmlns=... &gt;
	///		...
	///		&lt;activation&gt;
	///			&lt;tasks&gt;
    ///				&lt;task type="Microsoft.ApplicationBlocks.Mobile.Updater.ActivationProcessors.WaitForApplicationExitProcessor, Microsoft.ApplicationBlocks.Mobile.Updater" /&gt;
	///				&lt;task type="Microsoft.ApplicationBlocks.Mobile.Updater.ActivationProcessors.CABUpdateProcessor, Microsoft.ApplicationBlocks.Mobile.Updater" /&gt;
	///			&lt;/tasks&gt;
	///		&lt;/activation&gt;
	///	&lt;/manifest&gt;
	///	</code>
	///	This example continues the application deploy inside an external process.
	/// </example>	
	public class WaitForApplicationExitProcessor : IActivationProcessor
	{
		#region Private members

		/// <summary>
		/// The UpdaterTask instance provided in the Init method.
		/// </summary>
		private UpdaterTask taskToProcess;

        /// <summary>
        /// The name of the process that must be waited for.
        /// </summary>
        private string processName;

        /// <summary>
        /// The name of the process that will wait for the application.
        /// </summary>
        private const string waiterProcessName = "PostApplicationExitActivationProcess";

		#endregion

		#region Constructor

		/// <summary>
		/// Default constructor.
		/// </summary>
		public WaitForApplicationExitProcessor()
		{
		}
	
		#endregion
	
		#region IActivationProcessor Members

		/// <summary>
		/// Initializes the processor using the manifest configuration and the UpdaterTask instance.
		/// </summary>
		/// <param name="config">The configuration for the processor in the manifest file.</param>
		/// <param name="task">The UpdaterTask instance.</param>
		public void Init(ActivationProcessorProviderData config, UpdaterTask task)
		{
			taskToProcess = task;

            if (config.AnyAttr != null)
            {
                foreach (XmlAttribute attr in config.AnyAttr)
                {
                    if (String.Compare(attr.Name, "processName", false, CultureInfo.InvariantCulture) == 0)
                    {
                        processName = attr.Value;
                    }
                }
            }
		}

		/// <summary>
		/// Executes the processor.
		/// </summary>
		/// <remarks>This processor does not implement this method because all the operations are performed in the 
		/// PrepareExecution method.</remarks>
		public void Execute()
		{
		}

		/// <summary>
		/// If the activation fails this method is called to revert the operations performed by the processor.
		/// </summary>
		/// <remarks>Method not implemented because no error handling is required.</remarks>
		public void OnError()
		{
		}

		/// <summary>
		/// Prepares the execution and throws an exception if the execution is not possible.
		/// </summary>
		/// <remarks>
		/// Uses a mutex to validate that no other WaitForApplication method is already 
		/// executing. It also checks whether the application is already executing as a 
		/// process. If the application is running, a new process is started to wait for 
		/// the application and an exception is thrown to abort the execution. If the 
		/// process is not running, the activation continues.
		/// </remarks>
		public void PrepareExecution()
		{
            // Use a mutex to avoid running twice or more in a single application
            // we don't own the mutex here, since ownership is taken by the waiting process
            bool createdNew = false;
            string mutexName = String.Format(CultureInfo.InvariantCulture,
                "WaitApplication_{0}", taskToProcess.Manifest.Application.ApplicationId);
            MutexEx waitMutex = new MutexEx(false, mutexName, ref createdNew);
            if (!createdNew)
            {
                return;
            }

            // Look out for the process id to wait for based on the configuration
            int processId = GetProcessIdToWaitFor();

            // If we can't find the running process, we can continue with the activation.
            if (processId == -1)
            {
                return;
            }
			
            //// Copy the configuration file for the WaitApplication process
            //string newConfig = Path.Combine( 
            //    Path.GetDirectoryName( AppDomain.CurrentDomain.SetupInformation.ConfigurationFile ),
            //    waiterProcessName + ".exe.config" );

            //File.Copy( AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
            //        newConfig, true );

            // Extract the embedded executables
            string externalProcessor = GetApplicationDirectory() + "\\" + waiterProcessName + ".exe";
            ExtractResourceStream("Microsoft.ApplicationBlocks.Mobile.Updater.Resources.PostApplicationExitActivationProcess.exe", externalProcessor);

            if (!File.Exists(externalProcessor))
            {
                throw new InvalidOperationException("External Processor executable "+ waiterProcessName + ".exe not found in application folder");
            }

			// The application Id the processor must process
			string arguments = String.Format( CultureInfo.InvariantCulture, "{0} {1}", processId, taskToProcess.Manifest.Application.ApplicationId );
            ProcessStartInfo psi = new ProcessStartInfo(externalProcessor, arguments);

			// Spawn the external process
			Process.Start( psi );

			// Throw this exception to say "no, we won't run now"
			throw new ActivationPausedException();
		}

		#endregion

		#region Private members

        private string GetApplicationDirectory()
        {
            return Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName);
        }

		/// <summary>
		/// Extracts the files needed for waiting for a new process (which are included as embedded resources) in the 
		/// local folder.
		/// </summary>
		/// <param name="resourceName">The name of the resource to extract.</param>
		/// <param name="targetFile">The name of the file where the embedded resource will be saved.</param>
		/// <remarks>This method is a helper used to extract the two files included as embedded resources.</remarks>
		private void ExtractResourceStream( string resourceName, string targetFile )
		{
			// Get the assembly resources
			string[] manifestResourceNames = Assembly.GetExecutingAssembly().GetManifestResourceNames();
			using(Stream res = Assembly.GetExecutingAssembly().GetManifestResourceStream( resourceName ) )
			{
				using( FileStream fs = new FileStream( targetFile, FileMode.Create, FileAccess.Write ) )
				{
					using( BinaryWriter bw = new BinaryWriter( fs ) )
					{
						byte[] buffer = new byte[1024];
						int cbRead = 0;
						do
						{
							cbRead = res.Read( buffer, 0, 1024 );
							bw.Write( buffer, 0, cbRead );
						} while ( cbRead >= 1024 );
					}
				}
			}
		}

        /// <summary>
        /// Finds the process id using the name of the process.
        /// </summary>
        /// <returns>The id of the process.</returns>
        private int GetProcessIdToWaitFor()
        {
            if (processName == null)
            {
                // Assumes the application that must be waited is the current application.
                return Process.GetCurrentProcess().Id;
            }
            //else
            //{
            //    // Finds the process in the operating system information.
            //    foreach (var process in ProcessHelper.GetProcesses())
            //    {
            //        if (process.Name == processName)
            //        {
            //            return process.Id;
            //        }
            //    }
            //    //Process[] processes = Process.GetProcessesByName(processName);
            //    //if (processes.Length > 0)
            //    //{
            //    //    return processes[0].Id;
            //    //}
            //}
            return -1;
        }

		#endregion
	}
}
